import TJavaLib.TList;
import TJavaLib.TGraphic;
import TJavaLib.TPoint;
import java.awt.Color;
import java.awt.Font;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.text.DecimalFormat;

public class TGateway extends TNode {
	private static int gwID = 0;
	private DecimalFormat dF = new DecimalFormat("#,##0.00", new DecimalFormatSymbols(Locale.US));
	
	public static void resetID() { gwID = 0; }
	
	public TGateway(TSimulator sim) {
		super(TNode.GATEWAY);
		simulator = sim;
		
		radio.setTX(TRadio.DF_RTX);
		
		radio.setAddr("gw"+String.valueOf(gwID));
		gwID++;
	}
	
	//----------------------------For users-----------------------------------------
	public void bootup() {
		//Add task
		addTaskInTime(TTask.GSMR_ROUTING, 1000);
		addTaskInTime(TTask.HELLO_PROTOCOL, 10);
		
		//addTaskInTime(TTask.PRINT_NB_TABLE, 2000);
	}
	
	public void callback(byte taskName) {
		switch (taskName)	{
			case TTask.HELLO_PROTOCOL:
				//System.out.print("At time: "+simulator.getSimTime()+", ");
				//System.out.println(getAddr()+" broadcasts a HELLO packet");
				THelloPacket hpk = new THelloPacket(getAddr(), loc);
				send(hpk);
				
				//Continue to add this task for the next send
				//addTaskInTime(TTask.HELLO_PROTOCOL, 1000);
				break;
		
			case TTask.GSMR_ROUTING:
				//Get the destination group
				TList net = simulator.getNetwork().getNetwork();
				TList dAddrs = new TList();
				TList dCoors = new TList();
				for (int i=0; i<net.len(); i++) {
					TNode node = (TNode)net.get(i);
					if (node.getNodeType()==TNode.SENSOR) {
						TSensor sensor = (TSensor)node;
						if (sensor.getType()==TSensor.DESTINATION) {
							dAddrs.enList(sensor.getAddr());
							dCoors.enList(sensor.getLoc());
						}
					}
				}
				
				//Sort the des group in increasing order of angle
				int ndes = dCoors.len();
				TList phiList = new TList();
				for (int i=0; i<ndes; i++) {
					TPoint mi = (TPoint)dCoors.get(i);
					double phii = Math.atan2(mi.y-loc.y, mi.x-loc.x);
					if (phii < 0) phii += 2*Math.PI;
					phiList.enList(Double.valueOf(phii));
				}
				
				/*/Debug
				System.out.print("Coor list: ");
				for (int i=0; i<ndes; i++) {
					TPoint mi = (TPoint)dCoors.get(i);
					String mx = TUtil.dF.format(mi.x).toString();
					String my = TUtil.dF.format(mi.y).toString();
					System.out.print((String)dAddrs.get(i)+"-("+mx+","+my+"); ");
				}
				System.out.println();
				//End Debug*/
				/*/Debug
				System.out.print("Phi list: ");
				for (int i=0; i<phiList.len(); i++) {
					double phi = ((Double)phiList.get(i)).doubleValue();
					System.out.print((String)dAddrs.get(i)+"-"+TUtil.dF.format(phi).toString()+", ");
				}
				System.out.println();
				//End Debug*/
				
				TList sortedCoors = new TList();
				TList sortedAddrs = new TList();
				while (phiList.len() > 0) {
					int minj = 0;
					double minphi = ((Double)phiList.get(0)).doubleValue();
					for (int j=1; j<phiList.len(); j++) {
						double phij = ((Double)phiList.get(j)).doubleValue();
						if (minphi > phij) {
							minj = j;
							minphi = phij;
						}
					}
					sortedCoors.enList((TPoint)dCoors.get(minj));
					sortedAddrs.enList((String)dAddrs.get(minj));
					phiList.deList(minj);
					dCoors.deList(minj);
					dAddrs.deList(minj);
				}
				dCoors = sortedCoors;
				dAddrs = sortedAddrs;
			
				//Debug, Print debug info
				System.out.print("At time: "+simulator.getSimTime()+", ");
				System.out.print(getAddr()+" sends a packet to |");
				for (int i=0; i<dAddrs.len(); i++) {
					TPoint mi = (TPoint)dCoors.get(i);
					String mx = TUtil.dF.format(mi.x).toString();
					String my = TUtil.dF.format(mi.y).toString();
					System.out.print((String)dAddrs.get(i)+"-("+mx+","+my+"); ");
				}
				System.out.println();
				//End Debug*/
				
				/*---GSMR packet to be sent out---*/
				//This subgroup is actually the entire multicast group
				sg_struct group = new sg_struct(); 
				group.dAddrs = dAddrs;
				group.dCoors = dCoors;
				//The next relay node of this group is the gateway itself
				group.rAddr = getAddr();
				group.lastRepCoor = loc;
				group.varphi = calVarphi(loc, group.dCoors);
				group.d2des = d2(group.dCoors);
				//The list of subgroup, here it contains the only multicast group
				TList sgList = new TList();
				sgList.enList(group);
				TGSMRPacket packet = new TGSMRPacket(sgList);
				packet.sAddr = getAddr();
				packet.fAddr = packet.sAddr;
				//Pass the packet to the routing engine
				processPacket(packet);
				break;
				
			case TTask.PRINT_NB_TABLE:
				System.out.print("At time: "+simulator.getSimTime()+", ");
				System.out.print(getAddr()+" neighbors: |");
				for (int i=0; i<nbAddrs.len(); i++) {
					String nbAddr = (String)nbAddrs.get(i);
					System.out.print(nbAddr+"|");
				}
				System.out.println();
				break;
			
			default: break;
		}
	}
	
	public void processPacket(TPacket packet) {
		switch (packet.getType()) {
			
			/*---------------------Implementation of the HELLO Protocl-----------------------------*/		
			case TPacket.HELLO:
				THelloPacket hpk = (THelloPacket)packet;
				if (TUtil.strIn(nbAddrs, hpk.sAddr) < 0) {
					nbAddrs.enList(hpk.sAddr);
					nbCoors.enList(hpk.sCoor);
				}
				break;
				
			/*---------------------Implementation of the GSMR algorithm-----------------------------*/		
			case TPacket.GSMR:
				TGSMRPacket pkGSMR = new TGSMRPacket((TGSMRPacket)packet);
				
				//List of responsible subgroups
				TList subGroupList = new TList();
				for (int i=0; i<pkGSMR.sgList.len(); i++) {
					sg_struct sg = (sg_struct)pkGSMR.sgList.get(i);
					
					//Debug
					//Check if I am one of the destinations, then announce
					if (TUtil.strIn(sg.finalAddrs, getAddr()) >= 0)
						System.out.println("DESTINATION "+getAddr()+" RECEIVES A PACKET DESTININED TO IT!");
					//End debug
					
					//Check if I am one of the relay nodes
					if (getAddr().equalsIgnoreCase(sg.rAddr))
						subGroupList.enList(sg);
				}
				if (subGroupList.len() > 0) {
					for (int k=0; k<subGroupList.len(); k++) {
						sg_struct subGroup = (sg_struct)subGroupList.get(k);
						
						//Debug
						System.out.println("-----------------------------------------------");
						System.out.println("At "+getAddr()+":");
						System.out.println(getAddr()+" receives packet from "+pkGSMR.fAddr);
						//End Debug*/		
						
						//Exclude all immediate reachable destinations from subgroup
						subGroup.finalAddrs = new TList();
						for (int i=0; i<nbAddrs.len(); i++) {
							String nbAddr = (String)nbAddrs.get(i);
							int pos = TUtil.strIn(subGroup.dAddrs, nbAddr);
							if (pos >= 0) {
								subGroup.dAddrs.deList(pos);
								subGroup.dCoors.deList(pos);
								subGroup.finalAddrs.enList(nbAddr);
							}
						}
						//Debug
						System.out.print("finalAddrs: ");
						for (int i=0; i<subGroup.finalAddrs.len(); i++)
								System.out.print((String)subGroup.finalAddrs.get(i)+", ");
						System.out.println();
						//End Debug*/
						//Debug
						System.out.print("Remaining dAddrs: ");
						for (int i=0; i<subGroup.dAddrs.len(); i++)
								System.out.print((String)subGroup.dAddrs.get(i)+", ");
						System.out.println();
						//End Debug*/
					
						//Recalculate forwarding line for the subgroup as
						//some destinations have been removed
						if (subGroup.finalAddrs.len() > 0)
							subGroup.varphi = calVarphi(subGroup.lastRepCoor, subGroup.dCoors);

						double delta_app = 0.85;
						TGSMRPacket newPacket = new TGSMRPacket(new TList());
						newPacket.fAddr = getAddr();
						newPacket.sAddr = pkGSMR.sAddr;
						if (subGroup.dAddrs.len() > 0) {
							//Check if I am also the replication node for my subgroup
							double phi_size = phiSize(subGroup.dCoors);
							double d = d2(subGroup.dCoors);
							if (phi_size >= 2*Math.asin(radio.getTX()/d/delta_app)) {
								//Debug
								System.out.println(getAddr()+" is also a replicator");
								//End Debug*/
					
								//Perform GSGD algorithm
								TList sgCoorList = new TList();
								TList sgAddrList = new TList();
								while (subGroup.dCoors.len() > 1) {
									TList sgCoors = new TList();
									TList sgAddrs = new TList();
									int gsize = subGroup.dCoors.len();
									boolean found = false;
									int minpos = Integer.MAX_VALUE;
									int minsize = Integer.MAX_VALUE;
									while (!found && gsize>0) {
										//System.out.println("gsize: "+gsize);
										//Find minimum sg
										double minphi = Double.MAX_VALUE;
										TList minsg = null;
										int ngroup = 1;
										if (gsize < subGroup.dCoors.len())
											ngroup = subGroup.dCoors.len()-1;
										for (int i=0; i<ngroup; i++) {
											TList tmp = buildSG(subGroup.dCoors, i, gsize);
											double phisize = phiSize(tmp);
											if (minphi > phisize) {
												minphi = phisize;
												minsg = tmp;
												minpos = i;
												minsize = gsize;
											}
										}
							
										/*/Debug	
										System.out.print("minsg: ");
										for (int i=0; i<minsg.len(); i++) {
											TPoint mi = (TPoint)minsg.get(i);
											String mx = TUtil.dF.format(mi.x).toString();
											String my = TUtil.dF.format(mi.y).toString();
											System.out.print("("+mx+","+my+"); ");
										}
										System.out.println();
										//End Debug*/
					
										//Check the inequation (5)
										double dg = d2(minsg);
										/*/Debug
										System.out.println("dg: "+TUtil.dF.format(dg).toString());
										System.out.println("minphi: "+TUtil.dF.format(minphi).toString());
										//End of debug*/
										double comp = 0; 
										if (radio.getTX() < dg*delta_app)
											comp = 2*Math.asin(radio.getTX()/dg/delta_app);
										else
											comp = Math.PI/2;
										if (minphi < comp) {
											//System.out.println("Eq. 5 satisfied for gsize="+gsize);
											found = true;
											sgCoors = minsg;
										}
										gsize--;
									}
									//Add the found subgroup to sglist
									sgCoorList.enList(sgCoors);
									sgAddrs = buildSG(subGroup.dAddrs, minpos, minsize);
									sgAddrList.enList(sgAddrs);
						
									//Remove the found subgroup from the multicast group
									subGroup.dCoors = excludeSG(subGroup.dCoors, minpos, minsize);
									subGroup.dAddrs = excludeSG(subGroup.dAddrs, minpos, minsize);
								}
								if (subGroup.dCoors.len() == 1) {
									sgCoorList.enList(subGroup.dCoors);
									sgAddrList.enList(subGroup.dAddrs);
								}
					
								//Debug
								System.out.println("The group is divided into: ");
								for (int i=0; i<sgAddrList.len(); i++) {
									TList tmp = (TList)sgAddrList.get(i);
									System.out.print("Sub-group "+(i+1)+": |");
									for (int j=0; j<tmp.len(); j++)
										System.out.print((String)tmp.get(j)+"|");
									System.out.println();
								}
								//End Debug*/
			
								//Calculate the next relay node, varphi for each subgroup
								for (int i=0; i<sgCoorList.len(); i++) {
									sg_struct sg = new sg_struct();
									sg.dCoors = (TList)sgCoorList.get(i);
									sg.dAddrs = (TList)sgAddrList.get(i);
								
									sg.lastRepCoor = loc;
									sg.varphi = calVarphi(loc, sg.dCoors);								
												
									//Calculate relay node's coor
									TPoint rCoor = calRelayNode(sg);
									//Calculate relay node's address
									sg.rAddr = min2Coor(rCoor);
									sg.d2des = d2(sg.dCoors);
									System.out.println("D2des: "+TUtil.dF.format(sg.d2des).toString());
						
									//Add this sg_struct to the newPacket
									newPacket.sgList.enList(sg);		
								}
								//Debug
								System.out.println("Routing info:");
								for (int i=0; i<newPacket.sgList.len(); i++) {
									System.out.print("Group "+(i+1)+": |");
									sg_struct sg = (sg_struct)newPacket.sgList.get(i);
									TList tmp = (TList)sg.dAddrs;
									for (int j=0; j<tmp.len(); j++)
										System.out.print((String)tmp.get(j)+"|");
									System.out.println(" has relay node "+sg.rAddr);
								}
								//End debug*/
							} else { //Simply forward the packet to the next relay node that lies in the forwarding line
								//Calculate relay node's coor
								TPoint rCoor = calRelayNode(subGroup);
								//Calculate relay node's address
								subGroup.rAddr = min2Coor(rCoor);
								System.out.println("Routing info, relay node: "+subGroup.rAddr);

								//Add this sg_struct to the newPacket
								newPacket.sgList.enList(subGroup);
							}
						} else { //Dont specify relay, coz all destinations are reached from here
							subGroup.rAddr = null;
							newPacket.sgList.enList(subGroup);
						}
					
						//Forward the packet
						//System.out.println("Forwarder: "+newPacket.fAddr);
						send(newPacket);

						//Debug
						System.out.println("-----------------------------------------------");
						//End Debug*/
					}
				}
				break;
			
			/*---------------------The GMR algorithm--------------------------------------------*/
			//Here you place the implementation of the GMR protocol. All the belonging assisting 
			//functions will be placed after the assisting functions of the GSMR implementation
				
			default: break;
		}
	}
	
	/*--------------------------Assisting Functions of GSMR Implementation----------------*/

	private double d2(TList dCoors) {
		double dg = 0;
		for (int i=0; i<dCoors.len(); i++) {
			TPoint mi = (TPoint)dCoors.get(i);
			dg += TPoint.dist(loc, mi);
		}
		dg /= dCoors.len();
		return dg;
	}
	
	private String min2Coor(TPoint coor) {
		String rs = null;
		double dmin = Double.MAX_VALUE;
		for (int i=0; i<nbCoors.len(); i++) {
			TPoint p = (TPoint)nbCoors.get(i);
			double d = TPoint.dist(coor, p);
			if (dmin > d) {
				dmin = d;
				rs = (String)nbAddrs.get(i);
			}
		}
		return rs;
	}
	
	private double calVarphi(TPoint loc, TList dCoors) {
		double varphi = 0;
		for (int i=0; i<dCoors.len(); i++) {
			TPoint mi = (TPoint)dCoors.get(i);
			double phi = Math.atan2(mi.y-loc.y, mi.x-loc.x);
			if (phi < 0) phi += 2*Math.PI;
			varphi += phi;
		}
		varphi /= dCoors.len();
		return varphi;
	}
	
	private TPoint calRelayNode(sg_struct sg) {
		TPoint rCoor = new TPoint();
		
		/*/Debug
		String lrcx = TUtil.dF.format(sg.lastRepCoor.x).toString();
		String lrcy = TUtil.dF.format(sg.lastRepCoor.y).toString();
		String locx = TUtil.dF.format(loc.x).toString();
		String locy = TUtil.dF.format(loc.y).toString();
		System.out.println("LastRepCoor: ("+lrcx+","+lrcy+"); Forwarder: ("+locx+","+locy+")");
		//End debug*/
		
		//Calculate the polar angle of the current forwading node
		double phi = Math.atan2(loc.y-sg.lastRepCoor.y, loc.x-sg.lastRepCoor.x);
		if (phi < 0) phi += 2*Math.PI;
		
		//Calculate its alpha to varphi
		double alpha = phi - sg.varphi;
		/*/Debug
		System.out.println("Phi: "+TUtil.dF.format(phi).toString()+"; Varphi: "+TUtil.dF.format(sg.varphi).toString()+"; Alpha: "+TUtil.dF.format(alpha).toString());
		//End debug*/
		
		//The distance between forwarder and last replication node
		double d = TPoint.dist(sg.lastRepCoor, loc);
		/*/Debug
		System.out.println("Distance to lastRepCoor: "+TUtil.dF.format(d).toString());
		//End debug*/
		
		//Calculate the projection of the forwarding node on the forwarding line
		double prx = sg.lastRepCoor.x + d*Math.cos(alpha)*Math.cos(sg.varphi);
		double pry = sg.lastRepCoor.y + d*Math.cos(alpha)*Math.sin(sg.varphi);
		/*/Debug
		System.out.println("Projection: ("+TUtil.dF.format(prx).toString()+", "+TUtil.dF.format(pry).toString()+")");
		//End debug*/
		
		//Now calculate the relay node
		rCoor.x = radio.getTX() * Math.cos(sg.varphi) + prx;
		rCoor.y = radio.getTX() * Math.sin(sg.varphi) + pry;
		/*/Debug
		System.out.println("rCoor: ("+TUtil.dF.format(rCoor.x).toString()+", "+TUtil.dF.format(rCoor.y).toString()+")");
		//End debug*/
		
		return rCoor;
	}
	
	private TList buildSG(TList group, int pos, int size) {
		int ndes = group.len();
		TList subgroup = new TList();
		int p = pos;
		for (int i=0; i<size; i++) {
			subgroup.enList(group.get(p));
			p++; if (p>=ndes) p=0;
		}
		return subgroup;
	}
	
	private TList excludeSG(TList group, int pos, int size) {
		int ndes = group.len();
		
		TList newgroup = new TList();
		int lEdge = pos;
		int rEdge = pos+size-1;
		if (rEdge >= ndes) rEdge -= ndes;
		for (int i=0; i<ndes; i++) {
			if (i<lEdge || i>rEdge)
				newgroup.enList(group.get(i));
		}
		return newgroup;
	}
	
	private double phiSize(TList sgCoor) {
		//Calculate the angles
		int ndes = sgCoor.len();
		double minphi = Double.MAX_VALUE;
		double maxphi = Double.MIN_VALUE;
		for (int i=0; i<ndes; i++) {
			TPoint mi = (TPoint)sgCoor.get(i);
			double phii = Math.atan2(mi.y-loc.y, mi.x-loc.x);
			if (phii < 0) phii += 2*Math.PI;
			if (minphi > phii) minphi = phii;
			if (maxphi < phii) maxphi = phii;
		}
		return Math.abs(maxphi-minphi);
	}
	
	/*-------------------------------End of GSMR Implementation-------------------------------*/

	
		
	/*--------------------------Assisting Functions of GMR Implementation---------------------*/
		
	/*--------------------------------End of GMR Implementation-------------------------------*/
}
